<?php

namespace Reezy\OAuth;

use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\SimpleCache\CacheInterface;
use Reezy\OAuth\Contract\OAuthFactoryInterface;
use Reezy\OAuth\Contract\OAuthManagerInterface;
use Reezy\OAuth\Contract\OAuthUserIdProviderInterface;
use function config;
use function http_build_query;
use function md5;
use function preg_replace;
use function rtrim;
use function stripos;
use function uniqid;

class OAuthManager implements OAuthManagerInterface
{
    /**
     * @var CacheInterface
     */
    protected $cache;
    /**
     * @var ResponseFactoryInterface
     */
    protected $responseFactory;
    /**
     * @var OAuthFactoryInterface
     */
    protected $oauthFactory;
    /**
     * @var OAuthUserIdProviderInterface
     */
    protected $userIdProvider;

    /**
     * @var array
     */
    protected $uaMappings;

    /**
     * OAuthLogin constructor.
     * @param CacheInterface $cache
     * @param ResponseFactoryInterface $responseFactory
     * @param OAuthFactoryInterface $oauthFactory
     * @param OAuthUserIdProviderInterface $userIdProvider
     */
    public function __construct(CacheInterface $cache, ResponseFactoryInterface $responseFactory, OAuthFactoryInterface $oauthFactory, OAuthUserIdProviderInterface $userIdProvider)
    {
        $this->cache = $cache;
        $this->responseFactory = $responseFactory;
        $this->oauthFactory = $oauthFactory;
        $this->userIdProvider = $userIdProvider;
        $this->uaMappings = config('oauth.ua-mappings', []);
    }


    public function login($name, $code): int
    {
        $oauth = $this->oauthFactory->get($name);

        $token = $oauth->getAccessToken($code);

        $userId = $this->userIdProvider->find($oauth->getName(), $oauth->getClientId(), $token);

        if (empty($userId)) {
            $userId = $this->userIdProvider->register($oauth->getName(), $oauth->getClientId(), $oauth->getUserInfo($token));
        }
        return $userId;
    }

    public function oauth(ServerRequestInterface $request, string $name = null): int
    {

        $oauthId = $request->getCookieParams()['oauth_id'] ?? null;

        if ($oauthId == null) {
            $oauthId = md5(uniqid("oauth", true));
            $uidKey = false;
            $userId = 0;
        } else {
            $uidKey = "oauth:$oauthId:uid";
            $userId = $this->cache->get($uidKey);
        }

        if (empty($userId)) {
            $oauth = $this->oauthFactory->get($name ?? $this->getName($request->getHeaderLine('user-agent')));

            $queryParams = $request->getQueryParams();
            $code = $queryParams[$oauth->getCodeKey()] ?? '';

            $isSilent = $oauth->isSupportSilentMode() && ($queryParams['oauth_silent'] ?? '1') === '1';

            $stateKey = "oauth:$oauthId:state";
            if (empty($code)) {
                $uidKey && $this->cache->delete($uidKey);

                $authUrl = $oauth->getAuthUrl((string)$request->getUri(), $this->makeState($stateKey), $isSilent);

                $response = $this->responseFactory->createResponse(302)->withAddedHeader('Location', $authUrl)
                    ->withAddedHeader('Set-Cookie', "oauth_id=$oauthId; HttpOnly");
                throw new OAuthRedirectException($response);
            }
            $this->checkState($stateKey, $queryParams['state'] ?? '');

            $token = $oauth->getAccessToken($code);
            $userId = $this->userIdProvider->find($oauth->getName(), $oauth->getClientId(), $token);

            if (empty($userId)) {
                if ($isSilent) {
                    $uidKey && $this->cache->delete($uidKey);
                    unset($queryParams[$oauth->getCodeKey()], $queryParams['state'], $queryParams['scope'], $queryParams['response_type']);

                    $queryParams['oauth_silent'] = '0';

                    $url = rtrim(preg_replace('/\?.*/', '', (string)$request->getUri()), '/') . '?' . http_build_query($queryParams);

                    $authUrl = $oauth->getAuthUrl($url, $this->makeState($stateKey), false);
                    $response = $this->responseFactory->createResponse(302)->withAddedHeader('Location', $authUrl);
                    throw new OAuthRedirectException($response);
                }
                $userId = $this->userIdProvider->register($oauth->getName(), $oauth->getClientId(), $oauth->getUserInfo($token));
                if (empty($userId)) {
                    throw new OAuthException('register failed');
                }
            }
        }

        $this->cache->set($uidKey, $userId, 3600);
        return $userId;
    }

    protected function getName($ua)
    {
        foreach ($this->uaMappings as $driver => $feature) {
            if (stripos($ua, $feature) !== false) return $driver;
        }
        // 可以重定向到一个登录页，选择登录方式或授权驱动
        throw new OAuthException("no match driver.");
    }

    private function makeState($stateKey)
    {
        $state = md5(uniqid());
        $this->cache->set($stateKey, $state, 300);
        return $state;
    }

    private function checkState($stateKey, $state)
    {
        if (empty($state)) {
            throw new OAuthException('state is required');
        }
        $cached = $this->cache->get($stateKey);
        if (!empty($cached)) {
            $this->cache->delete($stateKey);
            if ($cached !== $state) {
                throw new OAuthException('invalid state');
            }
        }
    }

}
